/**
 * @class 指示绘制器
 * @author DuskyHuang 圣子
 * @description 提供指引线、标识线等相关的渲染和逻辑。
*/

import { _decorator, Color, Vec3, Node, Component } from 'cc';
import { core, Mediator, utils } from 'cfw';
import { SceneGraph, MILKY_TO_SOLAR, LOCAL_TO_SOLAR, M31_TO_SOLAR, M33_TO_SOLAR, VIRGO_TO_SOLAR } from 'const';
import { attenuate } from 'algorithms';
import { MilkyWay, StellarGouldBelt } from 'prefabs';
import { ui, UIProxy, cosmos, DeepGalaxiesProxy, GizmosProxy, gizmos, constStars, ConstStarsProxy, visit } from 'proxies';
import { DeepSkyBuilder } from '../mediators/deep-sky-builder';
import { CosmosController } from '../controllers/cosmos-controller';
const { ccclass } = _decorator;

enum LineType {
    PERPENDICULAR = 1,
    MIDDLE_LINE = 2,
    DIRECT_LINE = 3,
}

@ccclass('cocosmos.IndicativeRenderer')
export class IndicativeRenderer extends Mediator {

    protected onLoad(): void { this._init()._listen(); }

    protected start(): void { }

    protected lateUpdate(dt?: time.sec): void {
        const { stellar, milky, local, virgo, pow10 } = cosmos,
            { showLines } = gizmos, { showStars, showConstLines } = constStars;
        if (stellar && showStars && showConstLines) this._drawConstellationsLines();
        if (stellar && showLines) this._drawStarsIndicativeLines();
        if ((-3 < pow10 && pow10 < 4.5) && showLines) this._drawTargetLine(dt);
        if (milky && showLines) this._drawDeepSkyIndicativeLines();
        if (local && showLines) this._drawLocalIndicativeLines();
        if (virgo && showLines) this._drawVirgoIndicativeLines();
        this._updateMilkyStructures();  // TODO 迁移
    }

    private _init(): this {
        this._deepSky = core.getMediator(DeepSkyBuilder);
        this._stellar = utils.findNode(STELLAR, COSMOS).getComponent(StellarGouldBelt);
        this._milkyWay = utils.findNode(MILKY, COSMOS).getComponent(MilkyWay);
        this._local = utils.findNode(LOCAL, COSMOS);
        this._virgo = utils.findNode(VIRGO, COSMOS);
        this._csc = core.getController(CosmosController);
        return this;
    }

    // 绘制星座连线
    private _drawConstellationsLines(): void {
        const configs = this._stellar.configs, renderer = this._csc.deepRenderer;
        for (const [cst, record] of this._stellar.constStars) {
            const { stars, lines } = configs[cst] as constellationConfig;
            if (stars.length <= 0) continue;
            const segments = lines.split(',');
            segments.forEach(segment => {
                const abs = segment.split('-');
                const [wps1, wps2] = abs.map((ab: alphabet) => {
                    let star = record[ab];
                    if (ab.includes("@")) {
                        const [oab, from] = ab.split('@');
                        star = this._stellar.constStars.get(from as cst)[oab];
                    }
                    if (!star) console.warn(cst, ab, 'not exist');
                    else return star.node.worldPosition;
                });
                if (!wps1 || !wps2) return;
                const lenSqr = Math.min(wps1.lengthSqr(), wps2.lengthSqr());
                const att = attenuate(lenSqr, ConstLineAtt, true);
                if (att > 0) {
                    color.set(CONST_LINES);
                    color.a *= ConstStarsProxy.instance.consLineStrengthen * att;
                    v3a.set(wps1);
                    v3b.set(wps2);
                    renderer.addLine(v3a, v3b, color);
                }
            });
        }
    }

    // 绘制恒星指示引线
    private _drawStarsIndicativeLines(): void {
        if (!notEmptyArr(this._stellar.stars)) return;
        // too many, only draw mag ∈ 0-4
        const pos = visit.transInfo.stellar?.refers?.from?.worldPosition ?? Vec3.ZERO;
        const above4 = this._stellar.stars.filter(start => start.config.mag <= 4);
        this._drawIndicativeLines(above4, STARS, LineType.PERPENDICULAR, pos);
    }

    // 绘制星云、星团指示引线
    private _drawDeepSkyIndicativeLines(): void {
        const to = Vec3.ZERO;
        this._drawIndicativeLines(this._deepSky.clusters, CLUSTER, LineType.PERPENDICULAR, to);
        this._drawIndicativeLines(this._deepSky.nebulas, NEBULA, LineType.PERPENDICULAR, to);
    }

    // 绘制本团内星系群引线
    private _drawLocalIndicativeLines(): void {
        const [FROM, TO] = LOCAL_LINES_RANGE, { pow10 } = cosmos;
        if (pow10 <= FROM || pow10 >= TO) return;
        const { locals } = this._deepSky, ls = this._local.worldScale.x;
        Vec3.multiplyScalar(local, LOCAL_TO_SOLAR, ls);
        Vec3.multiplyScalar(milky, MILKY_TO_SOLAR, ls * 0.01);
        Vec3.multiplyScalar(m31, M31_TO_SOLAR, ls);
        Vec3.multiplyScalar(m33, M33_TO_SOLAR, ls);
        this._drawIndicativeLine(milky, LocalGG, LineType.PERPENDICULAR, local);
        locals.forEach(galaxy => {
            const from = galaxy.node.worldPosition;
            switch (galaxy.config.cent) {
                case 'milky': this._drawIndicativeLine(from, COMPANION, LineType.MIDDLE_LINE, milky); break;
                case 'M31': this._drawIndicativeLine(from, COMPANION, LineType.MIDDLE_LINE, m31); break;
                case 'M33': this._drawIndicativeLine(from, COMPANION, LineType.MIDDLE_LINE, m33); break;
                default: this._drawIndicativeLine(from, LocalGG, LineType.PERPENDICULAR, local); break;
            }
        });
        this._drawScopeCenter(local, LocalGG, ls * LOCAL_CIRC_RADIUS);
    }

    // 绘制超团内星系团引线
    private _drawVirgoIndicativeLines(): void {
        const [FROM, TO] = VIRGO_LINES_RANGE, { pow10 } = cosmos;
        if (pow10 <= FROM || pow10 >= TO) return;
        const { virgos } = this._deepSky, ls = this._virgo.worldScale.x;
        Vec3.multiplyScalar(virgo, VIRGO_TO_SOLAR, ls);
        this._drawIndicativeLine(Vec3.ZERO, VirgoSC, LineType.PERPENDICULAR, virgo);
        virgos.forEach(galaxy => this._drawIndicativeLine(galaxy.node.worldPosition, VirgoSC, LineType.PERPENDICULAR, virgo));
        this._drawScopeCenter(virgo, VirgoSC, ls * VIRGO_CIRC_RADIUS);
    }

    private _drawScopeCenter(at: Vec3, color: Color, radius: number, fade: num.ratio = 0.5): void {
        circ.set(color);
        circ.a *= fade;
        const renderer = this._csc.deepRenderer;
        Vec3.copy(v3a, at).add3f(0, radius, 0);
        renderer.addLine(v3a, at, circ);
        renderer.addDisc(v3a, radius, circ, Segments.CIRCLE, false);;
        renderer.addDisc(at, radius * 2, circ, Segments.CIRCLE, false);;
    }
    private _drawIndicativeLine(toPos: Vec3, arg: Color, type: LineType, fromPos?: Vec3, given?: number): void {

        const distSqr = Vec3.squaredDistance(toPos, fromPos ?? Vec3.ZERO);
        const att = isNum(given) ? given : attenuate(distSqr,
            type === LineType.PERPENDICULAR ? IndicativeLineAtt : IndicativeDirectiveLineAtt, true);
        if (att <= 0) return;
        color.set(arg);
        const { x, y, z } = toPos;
        color.a *= att;
        const renderer = this._csc.deepRenderer;
        v3a.set(fromPos ?? Vec3.ZERO);
        v3b.set(x, y, z);
        v3m.set(x, v3a.y, z);
        Vec3.subtract(v3v, v3b, v3a);
        if (type === LineType.PERPENDICULAR) {
            v3b.y *= 0.97;
            renderer.addLine(v3a, v3m, color);
            renderer.addLine(v3m, v3b, color);
        } else if (type === LineType.MIDDLE_LINE) {
            Vec3.scaleAndAdd(v3a, v3a, v3v, 0.04);
            Vec3.scaleAndAdd(v3b, v3b, v3v, -0.02);
            renderer.addLine(v3a, v3b, color);
        } else {
            renderer.addLine(v3a, v3b, color);
        }
    }

    private _timer: number = 0;
    private _drawTargetLine(dt: time.sec): void {
        if (visit.transInfo?.stellar?.refers) {
            this._timer += dt;
            const { pow10 } = cosmos;
            const sun = this._stellar.sun.node;
            const { from, to, temp } = visit.transInfo.stellar.refers;
            const att = attenuate(pow10, [-3, -2, 3.5, 4.5], false);
            const TempTo = temp ?? to;
            if (!!from && !!TempTo && (this._timer % 0.5) > 0.25) {
                this._drawIndicativeLine(TempTo.worldPosition, IND_TARGET, LineType.DIRECT_LINE, from.worldPosition, att);
            }
            // if (from && (from === TempTo) && from !== sun) this._drawIndicativeLine(sun.worldPosition,
            //     IND_SOLAR, LineType.PERPENDICULAR, from.worldPosition, att);
            // if (TempTo && (from !== TempTo) && TempTo !== sun) this._drawIndicativeLine(TempTo.worldPosition,
            //     IND_SOLAR, LineType.PERPENDICULAR, sun.worldPosition, att);
            if (from !== sun && TempTo !== sun) {
                from !== sun && this._drawIndicativeLine(sun.worldPosition, IND_SOLAR, LineType.PERPENDICULAR, from.worldPosition, att);
                TempTo !== sun && this._drawIndicativeLine(sun.worldPosition, IND_SOLAR, LineType.PERPENDICULAR, TempTo.worldPosition, att);
            } else if (from === sun) {
                this._drawIndicativeLine(TempTo.worldPosition, IND_SOLAR, LineType.PERPENDICULAR, sun.worldPosition, att);
            } else {
                this._drawIndicativeLine(sun.worldPosition, IND_SOLAR, LineType.PERPENDICULAR, from.worldPosition, att);
            }
        }
    }

    private _drawIndicativeLines(comps: (ICulling & Component)[], arg: Color, type: LineType, pos?: Vec3): void {
        comps.forEach(comp => (comp.node.activeInHierarchy || comp.culling) && !comp.routing
            && this._drawIndicativeLine(comp.node.worldPosition, arg, type, pos));
    }

    private _updateMilkyStructures(): void {
        const [FROM, TO] = MILKY_STRUCT_RANGE, { pow10 } = cosmos;
        if (pow10 > FROM && pow10 < TO) {
            this._milkyWay.claimStructureLabelsIfNotExist(this._csc.deepSkyCamera);
            // const { language } = ui, 
            const sx = this._milkyWay.node.worldScale.x, sqr = sx * sx;
            this._milkyWay.structureLabels.forEach((label, idx) => {
                // label.string = (Structures[idx] as structure)[language == 'cn' ? 0 : 1];
                const lenSqr = Vec3.squaredDistance(Structures[idx][2] as Vec3, Vec3.ZERO) * sqr;
                label.alpha = attenuate(lenSqr, NameLabelAtt, true);
            });
        } else this._milkyWay.freeStructureLabels();
    }

    private _listen(): void { }
    private _csc: CosmosController;
    private _deepSky: DeepSkyBuilder;
    private _stellar: StellarGouldBelt;
    private _milkyWay: MilkyWay;
    private _local: Node;
    private _virgo: Node;
}

// constants & configs
const { ConstLineAtt } = ConstStarsProxy;
const { LineColor: { STARS, NEBULA, CLUSTER, COMPANION, LocalGG, VirgoSC, CONST_LINES, IND_TARGET, IND_SOLAR }, Segments } = GizmosProxy;
const { IndicativeLineAtt, NameLabelAtt } = UIProxy;
const IndicativeDirectiveLineAtt: attenuation = [0.03, 0.1, 0.8, 1.5];
const { MilkyWay: { Structures } } = DeepGalaxiesProxy;
const { COSMOS, STELLAR, MILKY, LOCAL, VIRGO } = SceneGraph;
const MILKY_STRUCT_RANGE = [3.0, 5.5];
const LOCAL_LINES_RANGE = [5, 7.8];
const VIRGO_LINES_RANGE = [7, 9.8];
const LOCAL_CIRC_RADIUS = 0.1;
const VIRGO_CIRC_RADIUS = 0.03;

// temp values
const v3a = new Vec3;
const v3b = new Vec3;
const v3m = new Vec3;
const v3v = new Vec3;
const milky = new Vec3;
const local = new Vec3;
const virgo = new Vec3;
const m31 = new Vec3;
const m33 = new Vec3;
const color = new Color;
const circ = new Color;